/*
 * Copyright (C) 2015-2017 Alibaba Group Holding Limited
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

#include <aos/aos.h>
#include <aos/cli.h>
#include <aos/ringbuffer.h>
#include <drv/i2s.h>
#include <pin_name.h>
#include <drv/codec.h>
#include <pinmux.h>

#include <aos/debug.h>
#include <media.h>
#include <http_client.h>
#include <yoc/mic.h>
#include "avutil/named_straightfifo.h"
#include "app_main.h"

#define TAG "yvtest"

#define TEST_I2S_IDX                2

#define EVT_SEND_COMPLETED          1
#define EVT_RECV_COMPLETED          (1 << 1)

// #define I2S_TEST

static int pcm_print_enable;
#if defined(I2S_TEST)
#define I2S_BUF_SIZE    (2 * 2 * 48 * 100)
static int mic_i2s_chan1, mic_i2s_chan2;
static int ao_i2s_idx;
static int i2s_inited = 0;
static aos_event_t g_evt_i2s;
static i2s_handle_t i2s_handle;
static uint8_t i2s_buf[I2S_BUF_SIZE] __attribute__ ((aligned(16)));
static uint8_t i2s_mem_dump[2 * 48 * 2 * 5000];
static int dump_offset = 0;
#endif

#if defined(I2S_TEST)
static void test_pin_i2s_init(void)
{
    drv_pinmux_config(PB0, PB0_I2S2_SCLK);
    drv_pinmux_config(PB1, PB1_I2S2_WSCLK);
    drv_pinmux_config(PB2, PB2_I2S2_SDA);
}

void pcm_i2s_send(uint8_t *data_send, int len)
{
    unsigned int flags;

    csi_i2s_send(i2s_handle, (uint8_t *)data_send, len);
}

static void i2s_event_cb_fun(int32_t idx, i2s_event_e event, void *arg)
{
    LOGD(TAG, "event %d", event);
    if (event == I2S_EVENT_RECEIVE_COMPLETE || event == I2S_EVENT_RX_BUFFER_FULL) {
        aos_event_set(&g_evt_i2s, EVT_RECV_COMPLETED, AOS_EVENT_OR);
    }

    // if (event == I2S_EVENT_SEND_COMPLETE || event == I2S_EVENT_TX_BUFFER_EMPYT) {
    //     i2s_send_flag = 1;
    // }
}

static int nsfifo_is_reof(nsfifo_t *fifo)
{
    int     ret;
    uint8_t reof;
    uint8_t weof;
    ret = nsfifo_get_eof(fifo, &reof, &weof);

    return (ret == 0) ? reof : 1;
}

#define RECV_CHUNK  (48*2*2*30)
static void i2s_recv_tsk(void *arg)
{
    int sample_rate = arg;
    uint8_t data_recv[RECV_CHUNK];
    uint32_t recv_len;
    nsfifo_t *fifo = NULL;
    unsigned int flags = 0;
    int      reof = 0;
    int      len;
    char *   pos  = NULL;
    static int tsk_running;
    int timeout_cnt = 0;
    char fifo_name[256];
    
    if (tsk_running) {
        printf("last still running\n");
        return;
    }
    tsk_running = 1;

    snprintf(fifo_name, 256, "fifo://i2stest?avformat=rawaudio&avcodec=pcm_s16le&channel=2&rate=%d", sample_rate);
    fifo = nsfifo_open(fifo_name, O_CREAT, 48000 / 1000 * 16 / 8 * 10 * 40);

    aui_player_stop(MEDIA_ALL);
    aui_player_config_t config = {0};
    config.resample_rate = 0;
    aui_player_config(&config);

    aos_msleep(100);
    aui_player_play(MEDIA_SYSTEM, fifo_name, 1);

    csi_i2s_enable(i2s_handle, 1);

    while (ao_i2s_idx) {
        recv_len = csi_i2s_receive(i2s_handle, (uint8_t *)data_recv, RECV_CHUNK);
        if (recv_len > 0) {
            timeout_cnt = 0;
            // if (dump_offset + recv_len <= 2 * 48 * 2 * 5000) {
            //     memcpy(i2s_mem_dump + dump_offset, data_recv, recv_len);
            //     dump_offset += recv_len;
            //     continue;
            // }

            // LOGD(TAG, "put data");
            int total_len = (int)recv_len;
            while (total_len > 0) {
                reof = nsfifo_is_reof(fifo); /** peer read reach to end */
                if (reof) {
                    LOGD(TAG, "named fifo read eof");
                    ao_i2s_idx = 0;
                    break;
                }

                int len = nsfifo_get_wpos(fifo, &pos, 500);
                if (len <= 0) {
                    continue;
                }

                len = MIN(len, total_len);
                memcpy(pos, data_recv + (recv_len - total_len), len);
                nsfifo_set_wpos(fifo, len);

                total_len -= len;
            }
            // LOGD(TAG, "put fifo end");
        } else if (++timeout_cnt > 500) {
            printf("no i2s data, task end**********\n");
            ao_i2s_idx = 0;
            break;
        }

        aos_msleep(10);
    }

    /* set write eof */
    nsfifo_set_eof(fifo, 0, 1);

    while (0 == nsfifo_is_reof(fifo)) {
        aos_msleep(100); //wait peer drain fifo
    }
    nsfifo_close(fifo);
    csi_i2s_enable(i2s_handle, 0);
    tsk_running = 0;
}


void i2s_init(int sample_rate)
{
    // aos_event_new(&g_evt_i2s, 0);
    test_pin_i2s_init();
    i2s_handle = csi_i2s_initialize(TEST_I2S_IDX, NULL, NULL);
    if (!i2s_handle) {
        printf("i2s init error\n");
        return;
    }

    int32_t ret;
    i2s_config_t i2s_config;
    memset(&i2s_config, 0, sizeof(i2s_config));
    
    if (sample_rate == 0xff) {
        i2s_config.cfg.left_polarity = I2S_LEFT_POLARITY_LOW;
        i2s_config.cfg.mclk_freq = I2S_MCLK_256FS;
        i2s_config.cfg.sclk_freq = I2S_SCLK_32FS;
        i2s_config.cfg.mode = I2S_MODE_TX_MASTER;
        i2s_config.cfg.protocol = I2S_PROTOCOL_I2S;
        i2s_config.cfg.tx_mono_enable = 0;
        i2s_config.cfg.width = I2S_SAMPLE_16BIT;
        i2s_config.rate = 16000;
        i2s_config.tx_buf = (uint8_t *)i2s_buf;
        i2s_config.tx_buf_length = I2S_BUF_SIZE;
        i2s_config.tx_period = 640 * 8;
        printf("i2s inited to master mode 16000 sample rate\n");
    } else {
        i2s_config.cfg.left_polarity = I2S_LEFT_POLARITY_LOW;
        i2s_config.cfg.mclk_freq = I2S_MCLK_256FS;
        i2s_config.cfg.mode = I2S_MODE_RX_SLAVE;
        i2s_config.cfg.protocol = I2S_PROTOCOL_I2S;
        i2s_config.cfg.sclk_freq = I2S_SCLK_32FS;
        i2s_config.cfg.rx_mono_enable = 0;
        i2s_config.cfg.width = I2S_SAMPLE_16BIT;
        i2s_config.rate = sample_rate;
        i2s_config.rx_buf = (uint8_t *)i2s_buf;
        i2s_config.rx_buf_length = I2S_BUF_SIZE;
        i2s_config.rx_period = 16 * 2 * 2 * 3 * 10;
        printf("i2s inited to slave mode %d sample rate\n", sample_rate);
    }

    ret = csi_i2s_config(i2s_handle, &i2s_config);
    if (ret != 0) {
        printf("i2s init error\n");
        return;
    }

    if (sample_rate == 0xff) {
        csi_i2s_enable(i2s_handle, 1);
    }

    printf("i2s function init OK\n");
}
#endif

#define PCM_REVISE_NONE     0
#define PCM_REVISE_HTTP     1

typedef struct {
    dev_ringbuf_t rb;
    int download_running;
} simu_mic_http_t;

static simu_mic_http_t g_simu_http;
static int g_pcm_revise_type = PCM_REVISE_NONE;

void pcm_hook_call(void *data, int len)
{
    if (g_pcm_revise_type == PCM_REVISE_NONE) {
        return;
    }

    if (g_pcm_revise_type == PCM_REVISE_HTTP) {
        while (g_simu_http.download_running && ringbuffer_available_read_space(&g_simu_http.rb) < len) {
            LOGW(TAG, "no enough data");
            aos_msleep(10);
        }

        int rlen = MIN(ringbuffer_available_read_space(&g_simu_http.rb), len);
        ringbuffer_read(&g_simu_http.rb, data, rlen);

        if (!g_simu_http.download_running && ringbuffer_available_read_space(&g_simu_http.rb) == 0) {
            g_pcm_revise_type = PCM_REVISE_NONE;
            printf("all simulate mic finished\n");
        }
    }
}

#define HTTP_RB_SIZE    (320 * 2000)
#define HTTP_CHUNK_SIZE (320 * 20)

void http_sink(void *arg)
{
    const char *url = (const char *)arg;
    char *buffer = aos_malloc_check(HTTP_CHUNK_SIZE);
    int content_length = 0, total_read_len = 0, read_len = 0;
    char *http_buf = aos_malloc_check(HTTP_RB_SIZE);

    ringbuffer_create(&g_simu_http.rb, http_buf, HTTP_RB_SIZE);

    if (!wifi_internet_is_connected()) {
        printf("network not connected!\n");
        goto http_stop;
    }

    g_simu_http.download_running = 1;
    LOGD(TAG, "start sinking http file %s", url);

    http_client_config_t config = {
        .url = url,
        //.event_handler = _http_event_handler,
        .timeout_ms = 10000,
        .buffer_size = HTTP_CHUNK_SIZE,
        // .cert_pem = ca_cert,
    };

    http_client_handle_t client = http_client_init(&config);
    if (client == NULL) {
        printf("http request init failed, url \"%s\"\n", url);
        goto http_stop;
    }

    http_errors_t err;
    if ((err = http_client_open(client, 0)) != HTTP_CLI_OK) {
        printf("Failed to open HTTP connection: 0x%x\n", err);
        goto http_stop;
    }

    content_length =  http_client_fetch_headers(client);
    while (total_read_len < content_length) {
        int avlen = g_simu_http.rb.length - ringbuffer_available_read_space(&g_simu_http.rb);
        
        if (avlen < 640 * 5) {
            aos_msleep(20);
            continue;
        }

        int rlen = MIN(avlen, HTTP_CHUNK_SIZE);

        read_len = http_client_read(client, buffer, rlen);
        if (read_len <= 0) {
            printf("Error read data\n");
        goto http_err;
        }

        ringbuffer_write(&g_simu_http.rb, (uint8_t *)buffer, read_len);
        total_read_len += read_len;
    }


http_err:    
    LOGD(TAG, "file sink finished");
    http_client_cleanup(client);

http_stop:
    aos_free(buffer);
    aos_free(http_buf);
    g_simu_http.download_running = 0;
}

static void timed_print_data(int16_t mic1, int16_t mic2, int16_t ref1, int16_t ref2)
{
    static int16_t last_mic1 = -32760;
    static int16_t last_mic2 = -32760;
    static int16_t last_ref1 = -32760;
    static int16_t last_ref2 = -32760;

    static long long last_ms = 0;

    if (last_ms == 0) {
        last_ms = aos_now_ms();
        last_mic1 = mic1;
        last_mic2 = mic2;
        last_ref1 = ref1;
        last_ref2 = ref2;
    } else {
        if (mic1 > last_mic1) {
            last_mic1 = mic1;
        }
        if (mic2 > last_mic2) {
            last_mic2 = mic2;
        }
        if (ref1 > last_ref1) {
            last_ref1 = ref1;
        }
        if (ref2 > last_ref2) {
            last_ref2 = ref2;
        }
        long long now = aos_now_ms();
        if (now - last_ms > 250) {
            printf("?,%05d,%05d,%05d,%05d\n",last_mic1 * 10000 / 32768, last_mic2 * 10000 / 32768, last_ref1 * 10000 / 32768, last_ref2 * 10000 / 32768);
            last_ms = aos_now_ms();
            last_mic1 = -32760;
            last_mic2 = -32760;
            last_ref1 = -32760;
            last_ref2 = -32760;
        }
    }
}

static void print_mic_data(char *data, int len)
{
    int chan_len = len / 2 / 4;     // mic1, mic2, ref1, ref2; short
    int mic[4] = {0};

    for (int i = 0; i < 4; ++i) {
        short max_pcm = -32760;

        for (int j = 0; j < chan_len; j++) {
            short val = *(short *)&data[i*2 + j*2*4];
            if (val > max_pcm) {
                max_pcm = val;
            }
        }
        mic[i] = max_pcm;
    }

    timed_print_data(mic[0], mic[1], mic[2], mic[3]);
}

#if defined(I2S_TEST)
static uint8_t data_buf[640 * 8]  __attribute__ ((aligned(4)));
static void i2s_send_chan(char *data, int len, int chan1, int chan2)
{
    short *chan_data = (short *)data_buf;
    int offset = 0;

    for (int i = 0; i < len/5/2; i++) {
        chan_data[offset++] = *(short *)&data[i*10 + chan1*2];
        chan_data[offset++] = *(short *)&data[i*10 + chan2*2];
    }

    extern void pcm_i2s_send(uint8_t *data_send, int len);
    pcm_i2s_send((uint8_t *)chan_data, offset * 2);
}
#endif

static void mic_debug_cb(mic_event_id_t evt_id, void *data, int len)
{
    if (pcm_print_enable && evt_id == MIC_EVENT_PCM_DATA) {
        print_mic_data((char *)data, len);
    }

#if defined(I2S_TEST)
    if (evt_id == MIC_EVENT_PCM_DATA) {
        if ((mic_i2s_chan1 > 0 && mic_i2s_chan1 < 6) &&
            mic_i2s_chan2 > 0 && mic_i2s_chan2 < 6) {
            i2s_send_chan((char *)data, len, mic_i2s_chan1-1, mic_i2s_chan2-1);
        }
    }
#endif
}

static void cmd_cxc_func(char *wbuf, int wbuf_len, int argc, char **argv)
{
    static int yv_debug = 0;

    if (argc < 2) {
        return;
    }

    if (strcmp(argv[1], "debug") == 0) {
        int level = atoi(argv[2]);

        yv_debug = level;
        if (yv_debug) {
            aui_mic_add_dbghook(mic_debug_cb);
            aui_mic_control(MIC_CTRL_START_PCM);
        } else {
            aui_mic_control(MIC_CTRL_STOP_PCM);
            aui_mic_del_dbghook();
        }
    } else if (strcmp(argv[1], "micrec") == 0) {
        if (strcmp(argv[2], "start") == 0) {
            if (argc > 3) {
                char *save_name = "rec.pcm";
                if (argc == 5) {
                    save_name = argv[4];
                }

                aui_mic_rec_start(argv[3], save_name);
                printf("mic rec start.\n");
                return;
            }
        } else if (strcmp(argv[2], "stop") == 0) {
            aui_mic_rec_stop();
            printf("mic rec stop\n");
            return;
        }
        printf("usage:\n");
        printf("ai micrec stop\n");
        printf("ai micrec start ws://192.168.1.102:8090\n");
        return;
    } else if (strcmp(argv[1], "simu_mic") == 0) {
        /*yv simu_mic 1 http://192.168.50.25:8000/1592214047_mic_rec.pcm*/
        int sim_type = atoi(argv[2]);

        if (sim_type == PCM_REVISE_NONE) {
            printf("close simulate mic input\n");
        } else if (sim_type == PCM_REVISE_HTTP) {
            if (argc < 3) {
                printf("no http url\n");
                return;
            }

            aos_task_new("http_sink", http_sink, argv[3], 1024 * 4);
            printf("simulate mic input from HTTP stream\n");
            aos_msleep(1000);
        } else {
            printf("no available simulate source\n");
            return;
        }

        g_pcm_revise_type = sim_type;
        return;
    }
#if defined(I2S_TEST)
    else if (strcmp(argv[1], "ao_test") == 0) {
        if (argc >= 3) {
            int sample_rate = atoi(argv[2]);
            
            if (sample_rate == 48000 || sample_rate == 16000) {
                if (!i2s_inited) {
                    i2s_inited = 1;
                    i2s_init(sample_rate);
                }

                ao_i2s_idx = 1;

                aos_task_new("ao_test", i2s_recv_tsk, (void *)sample_rate, 1024 * 8);
                printf("i2s test open");
            } else {
                ao_i2s_idx = 0;
                printf("ao test closed");
            }
        }
        return;
    }
#endif

    if (yv_debug == 0) {
        printf("run 'yv debug' first\n");
        return;
    }

    if (strcmp(argv[1], "print") == 0) {
        if (argc == 3) {
            pcm_print_enable = atoi(argv[2]);
        }
#if defined(I2S_TEST)
    } else if (strcmp(argv[1], "i2s_test") == 0) {
        if (argc >= 3) {
            int chan = atoi(argv[2]);
            
            if (chan == 0) {
                mic_i2s_chan1 = 0;
                mic_i2s_chan2 = 0;
                printf("i2s test closed");
            } else if (argc == 4) {
                if (!i2s_inited) {
                    i2s_inited = 1;
                    i2s_init(0xff);
                }

                mic_i2s_chan1 = chan;
                mic_i2s_chan2 = atoi(argv[3]);
            } else {
                printf("cmd err, e.g. yv i2s_test 1 2\n");
                return;
            }

            if (mic_i2s_chan1 && mic_i2s_chan2) {
                printf("i2s test open");
            }
        }
#endif
    } else if (strcmp(argv[1], "input_gain") == 0) {
        if (argc == 3) {
            codec_input_t codec = {0};
            codec.codec_idx = 0;
            int gain = atoi(argv[2]);

            if (gain < 0 || gain > 7) {
                printf("input gain should be between 0~7\n");
                return;
            }

            for (int i = 0; i < 5; i++) {
                codec.ch_idx = i;
                csi_codec_input_set_analog_gain(&codec, gain);
            }
            printf("input gain set successfully\n");
        }
    } else if (strcmp(argv[1], "output_gain") == 0) {
        if (argc == 3) {
            codec_output_t codec = {0};
            codec.codec_idx = 0;
            int gain = atoi(argv[2]);

            if (gain > 0 || gain < -31) {
                printf("output gain should be between -31~0\n");
                return;
            }

            csi_codec_output_set_analog_left_gain(&codec, gain);
            csi_codec_output_set_analog_right_gain(&codec, gain);
            printf("output gain set successfully\n");
        }
    }

    // }     
    // if (strcmp(argv[1], "wake") == 0) {
    //     if (argc == 3) {
    //         yv_wake_trigger(g_yv_handle, 0);
    //     }
    // } 
    // } else if (strcmp(argv[1], "micrec") == 0) {
    //     if (argc >= 3) {
    //         if (strcmp(argv[2], "start") == 0) {
    //             if (argc > 4) {
    //                 printf("mic rec start.\n");
    //                 g_mic_record = 1;
    //                 audio_record_start(argv[3], argv[4]);
    //                 yv_pcm_enable(g_yv_handle, 15);
    //             }
    //         } else if (strcmp(argv[2], "stop") == 0) {
    //             printf("mic rec stop\n");
    //             yv_pcm_enable(g_yv_handle, 0);
    //             audio_record_stop();
    //             g_mic_record = 0;
    //         } else {
    //             printf("cmd error\n");;
    //         }
    //     } else {
    //             printf("usage:\n");
    //             printf("yv micrec stop\n");
    //             printf("yv micrec start ws://172.16.33.208:8090 micdata.pcm\n");
    //     }
    // } else if (strcmp(argv[1], "sdspd") == 0) {
    //     if (argc == 4) {
    //         sdrec_call_sdspd(atoi(argv[2]), atoi(argv[3]));
    //     }
    // }

}

#if 0
static uint16_t get_pcm_min(uint8_t * buf, int size)
{
    int16_t *ptr = (int16_t *) buf;
    int count = size / 2;

    int16_t min_pcm = 32760;
    for (int i = 0; i < count; i++) {
        if (ptr[i] < min_pcm) {
            min_pcm = ptr[i];
        }
    }

    return min_pcm;
}
#endif



/**
 * 数据格式说明：
 * 使用yv_pcm_enable(g_yv_handle, 15)获得的数据
 * 算法1： DSP会传输 32 * 20 * 5 = 3200 字节的数据 (单包640)
 * 算法2： DSP会传输 32 * 16 * 5 = 2560 字节的数据 (单包512)
 * 1ms 32字节，20ms传输一次，共5通道
*/
// 
void cli_reg_cmd_yvdbg(void)
{
    static const struct cli_command cmd_info = {"yv", "yv debug", cmd_cxc_func};

    aos_cli_register_command(&cmd_info);

}

